/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 play_combat_walk.cpp

	$Header: /heroes4/play_combat_walk.cpp $

	$NoKeywords: $

 ************************************************************************/

#include "precompiled.h"
#include "play_combat_walk.h"

#include "battlefield.h"
#include "combat_actor_action.h"
#include "combat_creature.h"
#include "game_time.h"
#include "options.h"
#include "playing_sound.h"

// --------------------------------------------------------------
// class to play walk animation & move actor across battlefield.
// --------------------------------------------------------------
t_play_combat_walk::t_play_combat_walk( t_combat_creature& actor,
		                                t_combat_path const& path, 
										t_combat_creature_handler handler )
				  : t_counted_animation( actor.get_battlefield() ), m_actor( actor ), m_path( path ),
				    t_counted_idle_processor( 20 )
{
	// compute the total distance to travel, in subcells.
	m_end_handler = handler;
	m_step = m_path.size() - 1;

	if (actor.is_in_tower())
	{
		t_combat_path_point point = m_path[m_step];

		actor.remove_from_tower();
		m_battlefield.move_object( actor,
			                              point.last_point << k_battlefield_subcell_shift );
	}

	m_sound = actor.play_sound( k_combat_actor_action_walk, true );
	actor.clear_influence();
	begin_move();
}

t_play_combat_walk::~t_play_combat_walk()
{
	if (m_sound != 0)
	{
		m_sound->stop();
		m_sound = 0;
	}
}

// --------------------------------------------------------------
// class to play walk animation & move actor across battlefield.
// begin movement
// --------------------------------------------------------------
void t_play_combat_walk::begin_move()
{
	// compute the total distance to travel, in subcells.
	int i;
	int const prewalk_distance = m_actor.get_prewalk_distance();
	int const postwalk_distance = m_actor.get_postwalk_distance();
	int const walk_distance = m_actor.get_walk_distance();

	m_distance = 0;
	for (i = 0; i <= m_step; i++)
	{
		if (m_path[i].direction & 1)
			m_distance += k_battlefield_subcells_per_cell 
			              + k_battlefield_subcells_per_cell / 2;
		else
			m_distance += k_battlefield_subcells_per_cell;
	}
	
	// compute the # of walk cycle animations we will show.
	m_walk_cycles = (m_distance - prewalk_distance - postwalk_distance + walk_distance / 2)
		            / walk_distance;
	if (m_walk_cycles < 0)
		m_walk_cycles = 0;

	t_combat_path_point point = m_path[m_step];
	t_direction         direction = point.direction;
	int                 delta;
	t_battlefield&      battlefield = m_battlefield;

	m_animation_distance = prewalk_distance + postwalk_distance 
	 					    + walk_distance * m_walk_cycles;
	battlefield.set_current_direction_and_action( m_actor, direction, 
		                                          k_combat_actor_action_prewalk );
	calculate_threshold( k_combat_actor_action_prewalk );

	m_sum = m_increment;
	delta = m_sum / m_threshold;
	m_sum = m_sum % m_threshold;
	m_distance_to_next_cell = k_battlefield_subcells_per_cell - delta;

	t_map_point_2d offset = get_direction_offset( direction ) * delta;

	battlefield.move_object( m_actor, m_actor.get_position() + offset );
}


// --------------------------------------------------------------
// class to play walk animation & move actor across battlefield.
// --------------------------------------------------------------
void t_play_combat_walk::calculate_threshold( t_combat_actor_action_id action )
{
	int						 frame_count = m_actor.get_frame_count( action );
	t_direction				 direction   = m_actor.get_current_direction();

	switch (action)
	{
		case k_combat_actor_action_prewalk:
			m_increment = m_actor.get_prewalk_distance() * m_distance;
			m_threshold = m_animation_distance * (frame_count + 1);
			break;

		case k_combat_actor_action_walk:
			m_increment = m_actor.get_walk_distance() * m_distance;
			m_threshold = m_animation_distance * frame_count;
			break;
			
		case k_combat_actor_action_postwalk:
			m_increment = m_actor.get_postwalk_distance() * m_distance;
			m_threshold = m_animation_distance * (frame_count + 1);
			break;

		default:
			assert( false );
			break;
	}
	if (m_threshold == 0)
		m_threshold = 1;
	if (direction & 1)
		m_threshold += m_threshold >> 1;
	set_delay( 100000 / (m_actor.get_frames_per_second() * get_combat_animation_speed()) );
}

// --------------------------------------------------------------
// class to play walk animation & move actor across battlefield.
// end movement
// --------------------------------------------------------------
void t_play_combat_walk::end_move()
{
	t_battlefield& battlefield = m_battlefield;

	if (m_actor.has_action( k_combat_actor_action_base_frame ))
		battlefield.set_current_action( m_actor, k_combat_actor_action_base_frame );
	else
		battlefield.set_current_action( m_actor, k_combat_actor_action_wait );
	if (m_sound != 0)
	{
		m_sound->stop();
		m_sound = 0;
	}
	m_step = -1;
}

// --------------------------------------------------------------
// class to play walk animation & move actor across battlefield.
// handle tower entry
// --------------------------------------------------------------
void t_play_combat_walk::enter_tower()
{
	t_battlefield& battlefield = m_battlefield;

	while (m_step > 0 && m_path[m_step].is_tower)
		m_step--;

	t_map_point_2d position = m_path[m_step];

	position <<= k_battlefield_subcell_shift;
	battlefield.move_object( m_actor, position );
	if (m_path[m_step].is_tower)
	{
		m_actor.place_in_tower();
		end_move();
		return;
	}
	begin_move();
}


// --------------------------------------------------------------
// class to play walk animation & move actor across battlefield.
// --------------------------------------------------------------
void t_play_combat_walk::on_idle()
{
	t_uint32 current_time = get_time();
	t_uint32 next_time    = get_next_time() - get_delay();
	t_battlefield& battlefield = m_battlefield;

	while (elapsed_time( current_time, next_time ) >= 0)
	{
		t_combat_actor_action_id action	= m_actor.get_current_action();

		next_time += get_delay();
		set_next_time( next_time );
		// check if we have finished the last frame (we play one frame of the wait).
		if (m_step < 0)
		{
			if (action == k_combat_actor_action_wait
				|| action == k_combat_actor_action_base_frame )
			{
				m_actor.set_influence();
				suspend_idle_processing();
				animation_ended();
				battlefield.check_gate_close();
				assert( m_sound == 0 );
				m_end_handler( m_actor );
				return;
			}
		}
		else 
		{
			t_direction			direction   = m_actor.get_current_direction();
			t_combat_path_point next_point;
			int					delta;

			next_point = m_path[m_step];
			// check if we need to turn.
			if (next_point.direction != direction)
			{
				// perform the turn without moving.
				m_battlefield.set_current_direction( m_actor, next_point.direction );
				m_distance_to_next_cell = k_battlefield_subcells_per_cell;
				calculate_threshold( m_actor.get_current_action() );
				return;
			}
			// handle entry into tower
			if (next_point.is_tower)
			{
				enter_tower();
				return;
			}
			m_sum += m_increment;
			delta = m_sum / m_threshold;
			m_sum = m_sum % m_threshold;

			t_map_point_2d      offset		 = get_direction_offset( direction ) * delta;
			t_map_point_2d      new_position = m_actor.get_position() + offset;

			m_distance_to_next_cell -= delta;
			while (m_distance_to_next_cell <= 0 )
			{
				m_step--;
				m_distance_to_next_cell += k_battlefield_subcells_per_cell;
				if (m_step < 0)
				{
					new_position = next_point;
					new_position <<= k_battlefield_subcell_shift;
					break;
				}
				// check if next point involves a turn.
				next_point = m_path[m_step];
				if (next_point.direction != direction)
				{
					// if it does, force the actor to center in the cell, instead of moving
					// beyond the center.
					new_position = next_point;
					new_position <<= k_battlefield_subcell_shift;
					m_distance_to_next_cell = k_battlefield_subcells_per_cell;
				}
			}
			m_battlefield.move_object( m_actor, new_position );
		}

		int	frame		= m_actor.get_current_frame_num();
		int	frame_count = m_actor.get_frame_count();
		
		frame++;
		if (frame == frame_count && action == k_combat_actor_action_prewalk)
		{
			m_battlefield.set_current_action( m_actor, k_combat_actor_action_walk );
			if (m_walk_cycles == 0)
				calculate_threshold( k_combat_actor_action_postwalk );
			else
				calculate_threshold( k_combat_actor_action_walk );
			return;
		}
		if (frame == 1 && action == k_combat_actor_action_walk)
		{
			if (m_walk_cycles == 0)
			{
				m_battlefield.set_current_action( m_actor, k_combat_actor_action_postwalk );
				calculate_threshold( k_combat_actor_action_postwalk );
				return;
			}
			m_walk_cycles--;
		}
		if (frame == frame_count && action == k_combat_actor_action_postwalk)
		{
			t_map_point_2d new_position;

			end_move();
			new_position = m_path.front();
			new_position <<= k_battlefield_subcell_shift;
			battlefield.move_object( m_actor, new_position );
			return;
		}
		if (frame == frame_count)
			frame = 0;
		battlefield.set_current_frame_num( m_actor, frame );
	}
}
